<?php
namespace Swiftx\Component\Orm\Reflection;
use PDO;
use ReflectionClass;
use Swiftx\Component\Orm\DataBase\Column;
use Swiftx\Component\Orm\DataBase\Table;
use Swiftx\Component\Orm\Exceptions\Runtime;
use Swiftx\Interfaces\Orm\Query;
use Swiftx\Interfaces\Orm\Entity;
use Swiftx\Component\Orm\Exceptions\Error;
use Swiftx\Interfaces\Ioc\Container;

/**
 * 创建一个Manager对象
 * @package Swiftx\Interfaces\Orm
 */
class Schema {

    /**
     * 数据库连接
     * @var PDO
     */
    protected $connect;

    /**
     * 类发射对象
     * @var ReflectionClass
     */
    protected $reflection;

    /**
     * 数据表名称
     * @var string
     */
    protected $tableName;

    /**
     * 注入容器
     * @var Container
     * @auto-injection
     */
    protected $container;

    /**
     * 属性反射类
     * @var Property[]
     */
    protected $property = [];

    /**
     * 查询器
     * @var Query[]
     */
    protected $query = [];

    /**
     * Schema 构造器
     * @param string $class
     * @param PDO $connect
     * @throws Error
     */
    public function __construct(string $class, PDO $connect){
        if(!class_exists($class))
            throw new Error("类不存在:".$class);
        $this->reflection = new ReflectionClass($class);
        $this->tableName = str_replace('\\','_',$class);
        $this->connect = $connect;
        // 循环遍历字段配置信息
        foreach($this->reflection->getProperties() as $property){
            $temp = new Property($this, $property);
            if(!$temp->isSupport()) continue;
            $name = $property->getName();
            $this->property[$name] = $temp;
        }
    }

    /**
     * 设置表名称
     * @param string $name
     */
    public function setTable(string $name){
        $this->tableName = $name;
    }

    /**
     * 查看表名称
     * @return string
     */
    public function getTableName():string {
        return $this->tableName;
    }

    /**
     * 查看表名称
     * @param string $property
     * @return string
     * @throws Error
     */
    public function getTableFieldName(string $property):string {
        if(empty($this->property[$property]))
            throw new Error('属性不存在：'.$this->reflection->getName().'->'.$property);
        return $this->property[$property]->getColumnName();
    }

    /**
     * 创建数据表操作
     * @return Table
     */
    public function createTable():Table {
        $table = new Table($this->connect);
        foreach ($this->property as $property){
            $column = new Column();
            $column->setName($property->getColumnName());
            $column->setType($property->getColumnType());
            if($property->isPrimary())
                $column->setPrimary(true);
            if($property->isUnsigned())
                $column->setUnsigned(true);
            if($property->isNoNull())
                $column->setNoNull(true);
            if($property->hasDefault())
                $column->setDefault($property->getComment());
            if($property->hasComment())
                $column->setComment($property->getComment());
            $table->setColumn($column);
        }
        $table->setName($this->tableName);
        return $table;
    }

    /**
     * 获取主键
     * @return Property[]
     */
    protected function getPrimary():array {
        $result = [];
        foreach ($this->property as $property){
            if($property->isPrimary())
                $result[] = $property;
        }
        return $result;
    }

    /**
     * 生成实体对象
     * @param array $data
     * @return Entity
     */
    public function buildObject(array $data):Entity{
        $className = $this->reflection->getName();
        $object = $this->container->fetch($className);
        foreach($this->property as $property){
            $columnName = $property->getColumnName();
            $value = $data[$columnName];
            $property->injection($object, $value);
        }
        return $object;
    }

    /**
     * 获取实体
     * @param array ...$keys
     * @return null|Entity
     * @throws Error
     */
    public function fetchByPrimary(... $keys): ?Entity{
        $sql = /** @lang */'SELECT * FROM '.$this->tableName;
        $condition = [];
        foreach($this->getPrimary() as $property)
            $condition[] = $property->getColumnName().' = ?';
        $sql .= ' WHERE '.implode(' AND ',$condition);
        $statement = $this->connect->prepare($sql);
        if(!$statement->execute($keys))
            throw new Error('语句执行失败:'.$statement->queryString);
        $result = $statement->fetch(PDO::FETCH_ASSOC);
        if($result == null) return null;
        $object = $this->buildObject($result);
        $object->setLinked(true);
        return $object;
    }

    /**
     * 注册查询器
     * @param Query $query
     */
    public function registerQuery(Query $query){
        $this->query[$query->getName()] = $query;
    }

    /**
     * 获取查询器
     * @param string $name
     * @return Query
     */
    public function getQuery(string $name):Query{
        return $this->query[$name];
    }

    /**
     * 存储实体
     * @param Entity $entity
     * @throws Runtime
     */
    public function saveEntity(Entity $entity){
        // 对象数据提取
        $data = [];
        foreach($this->property as $property){
            $columnName = $property->getColumnName();
            $data[$columnName] = $property->read($entity);
        }
        // 处理新增对象
        if(!$entity->isLinked()){
            // 执行数据存储
            $keys = array_keys($data);
            $sql = /** @lang */'INSERT INTO '.$this->tableName;
            $sql .= ' (`'.implode('`,`', $keys).'`) ';
            $sql .= 'VALUES (:'.implode(',:', $keys).')';
            $statement = $this->connect->prepare($sql);
            if($statement->execute($data) === false)
                throw new Runtime($statement->errorInfo()[2], $statement->errorCode());
            $entity->setLinked(true);
        }
        // 更新对象数据
        else {
            $sql = /** @lang */'UPDATE '.$this->tableName.' SET ';
            $keys = array_keys($data);
            foreach($keys as &$value)
                $value = '`'.$value.'`=:'.$value;
            $sql .= implode(',' , $keys).' WHERE ';
            $keys = [];
            foreach ($this->getPrimary() as $property)
                $keys[] = '`'.$property->getColumnName().'`=:'.$property->getColumnName();
            $sql .= implode(' AND ' , $keys);
            $statement = $this->connect->prepare($sql);
            if($statement->execute($data) === false)
                throw new Runtime($statement->errorInfo()[2], $statement->errorCode());
        }
    }

    /**
     * 调试信息输出
     * @return array
     */
    public function __debugInfo(){
        $result['table'] = $this->tableName;
        $result['map'] = $this->reflection->getName();
        $result['query'] = $this->query;
        return $result;
    }

}